Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scorekeeper Refactoring 1/4 #2934

Open
wants to merge 3 commits into
base: staging
Choose a base branch
from

Conversation

vovacha
Copy link

@vovacha vovacha commented May 17, 2024

This pull request initiates the refactoring of the Scorekeeper and Gateway components. The goal of this refactoring is to simplify the codebase, reduce technical debt, and improve maintainability and scalability. This is the first stage of a multi-step process to achieve a cleaner and more modular architecture.

Problems:

  • Locality Issues. The Job class has several injections, such as RegisterHandler and cron/StartCronJobs/startJob, causing locality issues.
  • Complexity. There is excessive complexity with too many abstraction layers, including MonolithJobRunner, MicroserviceJobRunner, JobsRunnerFactory, JobsRunner, JobFactory, and individual classes for each job.
  • Dead Code. There is commented-out code, unused event handlers in Scorekeeper (only job events are used), and MicroserviceJobRunner is particularly problematic.
  • Tight Coupling with Gateway. The Scorekeeper directly feeds the Gateway with job statuses.

Final goal of the Scorekeeper refactoring:

  • Simplify Abstractions. Reduce all abstractions to a single Job class. The state will be represented by the DB layer only.
  • Unified Flow. Ensure a consistent flow from JobConfig to Job class instance to job.start().
  • Organize Specific Jobs. Maintain specific jobs in separate locations, similar to the current structure, with minor changes.
  • Gateway Independence. Ensure the Gateway consumes data only from the DB, eliminating its dependency on the Scorekeeper.

Current PR changes:

  • scorekeeper.ts. Minor changes. A new entry point and syntax improvements.
  • JobConfigs.ts. Simplified the job initialization process. The JobNames enum will be removed in a future update.
  • types.ts. New file. Contains existing types related to the Job class.
  • Job.ts. New file. Contains only the Job class, supports the old public interfaces, and explicitly shows dependency injection for the startJobFunction, which will be removed later.
  • specificJobs. Made minor changes to create a common interface by supporting the metadata parameter, even if it's not used.
  • RegisterHandlers.ts. Removed dead code and moved job events to the Job class.
  • scorekeeper.int.test.ts. Added an integration test to check the job status update on event emission.
  • JobsClass.ts. Deleted. This file exists temporarily to minimize the impact of the current PR and will be removed in a future update.
  • MonolithJobRunner.ts, MicroserviceJobRunner.ts, JobsRunnerFactory.ts, JobRunner.ts, JobFactory.ts. Deleted.

Potential next steps (to be discussed):

  • Scorekeeper 1. Current PR.
  • Scorekeeper 2. Localize behavior by bringing cron job initialization into the Job class. Remove JobNames enum. Scorekeeper Refactoring 2/4 #2937
  • Scorekeeper 3. Add DB queries to store job status.
  • Gateway. Update views to retrieve job statuses from the DB.
  • Gateway. Remove Scorekeeper dependency.
  • Core. Update Gateway initialization without Scorekeeper.
  • Scorekeeper 4. Remove status updates via events and eliminate public interfaces related to the Gateway.
  • Gateway. Reduce abstraction layers and code duplication in controllers.
  • Gateway. Refactor dynamic routes (e.g., onlyHealth flag).
  • Deployment Update. Update deployment scripts to run services separately: Scorekeeper, Gateway, and Telemetry.

@@ -235,6 +219,14 @@ export default class ScoreKeeper {
return true;
}

startJobs(metadata: JobRunnerMetadata) {
this._jobs = jobConfigs.map((config) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a way to control which job to start, instead of all of them ? good for local testing purposes
With the JobFactory you could control that by commenting out the specif job you didn't want

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The best would be to control the jobs from the config. For example, with isActive flag.
As for the JobFactory, with the current change, you can comment lines related to a particular JobConfig and achieve the same result. But of course, it's not how it should be in the first place. Config or env variables is the way to go.

  // {
  //   jobKey: "validity",
  //   defaultFrequency: Constants.VALIDITY_CRON,
  //   jobFunction: validityJobWithTiming,
  //   name: JobNames.Validity,
  // },
  {
    jobKey: "score",
    defaultFrequency: Constants.SCORE_CRON,
    jobFunction: scoreJobWithTiming,
    name: JobNames.Score,
  },

| "finished"
| "errored"
| "started"
| "Not Running";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why Not Running starts with capital letters ?
They should probably be constants/enum defined somewhere rather than hardcoded strings, wdyt ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% agree, that's the plan.
I commented, There is a dependency on status names in scorekeeper-status-ui. I decided not to touch it just to minimize the impact of the current PR. Also, I need to check if more dependencies on this status name exist.

this.status = {
name: jobConfig.name,
updated: Date.now(),
status: "Not Running",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be of type StatusName, right ? can we avoid to have the string Not Running hardcoded ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I partially answered in the previous comment. Please let me know if you want me to include this in the current PR.
For the renaming issue, there is a dependency at least on scorekeeper-status-ui.
For the hardcoding issue, it's easier, but the plan was to tackle it together.

};
export const jobConfigs: JobConfig[] = [
{
jobKey: "activeValidator",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make sense to have these jobKeys defined as constants or enums instead of hardcoded strings ?

Copy link
Author

@vovacha vovacha May 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it absolutely makes sense. This question also related to the default cron strings, which are located in the constants.

Current state:

  • Config holds information about both job keys and cron strings.
  • Source code also holds information about the job keys and default cron strings. The source code assumes the config could be not valid or not full.

The question is - why do we have defaults for the cron strings? If we trust the config, then we should have only one source of truth.

I suggest:

  • Add an Enum containing all of the Job names (jobKey). It would be our source of truth.
  • Validate the config structure (contains all jobs keys, cron string, isActive flag, etc.).
  • Remove default cron strings from the source code.

This way we'd also simplify JobConfig type.

Instead of this👇 we'd need only mapping between jobKey and jobFunction:

  {
    jobKey: "activeValidator",
    defaultFrequency: Constants.ACTIVE_VALIDATOR_CRON,
    jobFunction: activeValidatorJobWithTiming,
    name: JobNames.ActiveValidator,
  },

Alternatively, we can leave default cron strings at the source code level. But store them closer to the Enum of job names or even in the same structure.

WDYT?

@@ -1,83 +0,0 @@
import { logger } from "../..//index";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the idea is to nuke everything related to the Microservice version, right ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but the idea of decoupling from the Gateway is still relevant. Implementing with bullmq is one option. BTW even with the queue implementation, I see no usage for that class. All the jobs would be initialized on the worker side and the worker becomes the new Scorekeeper in that case.

However, a simpler approach would be to leave everything as is and only change the job status handling. By persisting the job status in the DB and removing events, we can achieve similar results and decouple Scorekeeper from API. That's what I suggest.

Moving from this:
await createServer(config, handler, scorekeeper);
To this:
await createServer(config, handler);

This way API could get the job statuses directly from the DB and there would be no need to run everything tightly coupled.

@ironoa
Copy link
Contributor

ironoa commented May 17, 2024

Question: the scorekeeper is currently part of the common package. Is it really common or it deserves to have its own package ? wdyt ?

@vovacha
Copy link
Author

vovacha commented May 17, 2024

Question: the scorekeeper is currently part of the common package. Is it really common or it deserves to have its own package ? wdyt ?

It looks like common package is a Scorekeeper itself. Even the part responsible for the nomination is a job named MainScorekeeperJob. At the end of the day package common contains everything needed for the Cron jobs. And Scorekeeper is a Job runner by design.

Answering the question. It's already a separate package but with a weird name and locality/structure issues. What we can/should do is to make it work on its own. At the moment one of the responsibilities of the Scorekeeper class is to expose a public interface for the API (Gateway): https://github.com/w3f/1k-validators-be/blob/master/packages/gateway/src/routes/setupRoutes.ts#L69-L76

By removing this dependency we can run it on its own: in a separate process/container.

@ironoa Does it make sense to you?

@vovacha vovacha changed the title Refactor Scorekeeper 1 Scorekeeper Refactoring 1/4 May 19, 2024
@vovacha vovacha mentioned this pull request May 19, 2024
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants